/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/error.hpp>
#include <Windows.h>

namespace baja
{
class win32_error : public baja_error
{
public:
    win32_error(HRESULT hr, const std::string& msg) : baja_error(msg), _hr(hr) 
    {
        boost::format fmt = boost::format("win32 error: hr: 0x%08x (last error: %d) %s") % hr % GetLastError() % msg;
        _what = fmt.str();
    }

    win32_error(HRESULT hr, const char* msg) : baja_error(msg), _hr(hr)
    {
        boost::format fmt = boost::format("win32 error: hr: 0x%08x (last error: %d) %s") % hr % GetLastError() % (msg ? msg : "");
        _what = fmt.str();
    }

    HRESULT hr() const { return _hr; }

    const char* what() const throw()
    {
        return _what.c_str();
    }

    HRESULT _hr;
    std::string _what;
};

struct win32_result_nothrow
{
    HRESULT hr;

    explicit win32_result_nothrow(HRESULT hr_) : hr(hr_) {}
};

struct win32_result
{
    HRESULT hr;
    const char* msg;

    win32_result() : hr(S_OK), msg(nullptr) {}

    win32_result(const HRESULT hr) : msg(nullptr)
    {
        this->setHr(hr);
    }

    win32_result(const bool b) : msg(nullptr)
    {
        HRESULT hr = S_OK;
        if (!b)
        {
            hr = HRESULT_FROM_WIN32(::GetLastError());
            if (SUCCEEDED(hr))
            {
                hr = E_FAIL;
            }
        }
        this->setHr(hr);
    }

    win32_result(const win32_result_nothrow& r) : msg(nullptr)
    {
        this->setHr(r.hr, true); // nothrow
    }

    win32_result& operator=(const HRESULT hr)
    {
        this->setHr(hr);
        return *this;
    }

    win32_result& operator=(const bool b)
    {
        HRESULT hr = S_OK;
        if (!b)
        {
            hr = HRESULT_FROM_WIN32(::GetLastError());
            if (SUCCEEDED(hr))
            {
                hr = E_FAIL;
            }
        }
        this->setHr(hr);
        return *this;
    }

    win32_result& operator=(const win32_result_nothrow& r)
    {
        this->setHr(r.hr, true); // no throw
        return *this;
    }

    void setMessage(const char* msg)
    {
        this->msg = msg;
    }

private:
    inline void setHr(HRESULT hr, const bool noThrow = false)
    {
        this->hr = hr;
        if (FAILED(hr))
        {
            if (!noThrow)
            {
                BAJA_THROW(baja::win32_error(hr, this->msg));
            }
            else
            {
                // if not throwing, at least trace the error
                // NOTE: I am not going to do this trace since in theory, if we are using noThrow,
                // that means the code knows what it's doing and the failed HRESULT is a valid one
                // BAJA_TRACE(boost::format("WARNING: win32_result: setting HRESULT to failed value: 0x%08x. noThrow=true") % hr);
            }
        }
    }
};

} // namespace baja